import { Resource, ResourceUp, UpResourceDto } from '@boticord/resources-client'
import { User } from '@boticord/users-client'
import {
  CACHE_MANAGER,
  ForbiddenException,
  HttpException,
  Inject,
  Injectable,
  InternalServerErrorException,
  NotFoundException,
} from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { LessThanOrEqual, MoreThanOrEqual, Repository } from 'typeorm'
import { Cache } from 'cache-manager'
import { ErrorCodes, TimeLimits } from '@boticord/consts'
import { Cron } from '@nestjs/schedule'
import { ResourcesService } from './resources.service'
import { ConfigService } from '@nestjs/config'
import { ValidationUtil } from '../validation.util'

@Injectable()
export class UpsService {
  constructor(
    @InjectRepository(ResourceUp) private readonly ups: Repository<ResourceUp>,
    @InjectRepository(User) private readonly users: Repository<User>,
    @InjectRepository(Resource) private readonly resources: Repository<Resource>,
    @Inject(CACHE_MANAGER) private readonly cache: Cache,
    private readonly resourcesService: ResourcesService,
    private readonly config: ConfigService,
    private readonly validation: ValidationUtil,
  ) {}

  @Cron('*/10 * * * *') // at every 10th minute(14:10, 14:20 etc), NOT every ten minutes
  async clearExpiredUps() {
    const results = await this.ups.find({
      where: { expires: LessThanOrEqual(Date.now()) },
      relations: { resource: true },
    })

    await this.ups.delete(results.map(r => r.id))

    results.forEach(r => {
      this.resourcesService.indexate(r.resource.id)
    })
  }

  // checks if user can put an up on a resource
  async checkCooldown(user: string, resource: string): Promise<number> {
    /**
     * if up expires in 2 weeks, and up cd = 1 day, we need to check if there are any ups expires in the last 13 days+
     * if there are none, we can up
     * if there are, we need to divide from date.now() + up cd to the last up expires
     * thus we get the current up cd
     *
     * если ап исчезает через 2 недели, а кд на ап 1 день, значит нужно найти ап, который истекает через 13 дней+
     * если такого нет, значит можно апать ещё
     * если есть, значит нужно вычесть из текущей даты + время жизни апа дату, когда он истечёт
     * тогда мы получим сколько времени осталось до следующего апа
     */

    const currentUp = await this.ups.findOne({
      where: {
        user: { id: user },
        resource: { id: resource },
        expires: MoreThanOrEqual(Date.now() + (TimeLimits.UpTTL - TimeLimits.UpTimeout)),
      },
    })

    if (currentUp) {
      return ((currentUp.expires - TimeLimits.UpTTL) + TimeLimits.UpTimeout) - Date.now()
    }

    return 0
  }

  async unup(userID: string, resourceID: string) {
    // fetches the latest up
    const latestUp = await this.ups.findOne({ where: { user: { id: userID }, resource: { id: resourceID } } })
    if (latestUp === null) {
      throw new NotFoundException({ code: ErrorCodes.UnknownUp })
    }
    await this.ups.remove(latestUp)
    await this.resourcesService.indexate(resourceID)
  }

  async up(userID: string, resourceID: string, body: UpResourceDto, ip: string) {
    const user = await this.users.findOne({ where: { id: userID } })
    if (user === null) {
      throw new NotFoundException({ code: ErrorCodes.UnknownUser })
    }

    const resource = await this.resources.findOne({ where: { id: resourceID } })
    if (resource === null) {
      throw new NotFoundException({ code: ErrorCodes.UnknownResource })
    }

    const cd = await this.checkCooldown(userID, resourceID)
    if (cd) {
      throw new HttpException({ code: ErrorCodes.TooManyUps, result: { cd } }, 429)
    }

    if (process.env.NODE_ENV !== 'development') {
      const secret = this.config.get('TURNSTILE_SECRET')
      if (!secret) {
        throw new InternalServerErrorException({ code: ErrorCodes.ServiceConfiguredIncorrectly })
      }

      const response = await this.validation.validateTurnstile(secret, body.token, ip)
      if (!response.success) {
        throw new ForbiddenException({ code: ErrorCodes.AutomatedRequestsNotAllowed })
      }
    }
    
    const up = new ResourceUp()
    up.expires = Date.now() + TimeLimits.UpTTL
    up.resource = resource
    up.user = user
    
    const res = await this.ups.save(up)
    await this.resourcesService.indexate(resourceID)

    const upCount = await this.ups.count({ where: { resource: { id: resourceID } } })

    return { up: res, upCount }
  }
}
